/*
 * @Author: Mainul Islam
 */

package edu.uta.cse.randomgraph.graph;

import java.awt.Color;
import java.awt.Graphics;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import edu.uta.cse.randomgraph.graph.property.Node;
import edu.uta.cse.randomgraph.util.DirUtil;
import edu.uta.cse.randomgraph.util.LogType;
import edu.uta.cse.randomgraph.util.LogWriter;
import edu.uta.cse.randomgraph.util.MyDrawUtil;


public class ScaleFreeBAGraph extends MyGraph {
	
	private static String folderName = "Barabasi";

	private int initNumNodes, currNumNodes;
	private double erConProb;
	private int numEdgeToSelect;
	
	private List<Integer> edgeEndPointsList;
	private List<Integer> targetEndPointsList;
	
	private List<List<Integer>> selectedEdgeIndexList;
	private List<List<Integer>> selectedEndPointsList;
	private boolean drawSimulateGraph;
	
	public ScaleFreeBAGraph(int nodeNum) {
		super(nodeNum);
		numEdgeToSelect = 2;
		initNumNodes = numEdgeToSelect + 2;
		initGraph();
	}	
	
	public ScaleFreeBAGraph(int nodeNum, int initNodeNum, int edgeToSelect) {
		super(nodeNum);
		initNumNodes = initNodeNum;
		numEdgeToSelect = edgeToSelect;
		initGraph();
	}	
	
	private void initGraph() {
		erConProb = 1.0;
		currNumNodes = initNumNodes;
		drawSimulateGraph = false;
		edgeEndPointsList = new ArrayList<Integer>();
		selectedEdgeIndexList = new ArrayList<List<Integer>>();
		selectedEndPointsList = new ArrayList<List<Integer>>();
	}
	
	private void initPreCompute() {
		initDist();
	}

	private void preCompute() {
		computeFloydWarshal();
		computeDegreeList();
	}

	public void setSimulateGraph(boolean val) {
		this.drawSimulateGraph = val;
		contSimulation = this.drawSimulateGraph;
		currNumNodes = initNumNodes+1;
	}

	public void alterSimulateGraph() {
		this.drawSimulateGraph = !this.drawSimulateGraph;
		contSimulation = this.drawSimulateGraph;
		currNumNodes = initNumNodes+1;
	}

	public void generateGraph() {
		initPreCompute();
		
		ErdosRenyiGraph eg = new ErdosRenyiGraph(currNumNodes, erConProb);
		eg.generateGraph();
		loadGraph(eg);
		
		for(int i=currNumNodes; i<numNodes; i++) {
			addNodeToGraph();
			currNumNodes++;
		}
		
		preCompute();
	}

	private void loadGraph(ErdosRenyiGraph eg) {
		this.numEdges = eg.numEdges;
		
		int i, j;
		for(i=0; i<currNumNodes; i++) {
			nodes[i] = new Node();
			nodes[i].age = 0;
			nodes[i].deg = eg.nodes[i].deg;
			nodes[i].inDeg = eg.nodes[i].inDeg;
			nodes[i].outDeg = eg.nodes[i].outDeg;
			
			for(int ind: eg.nodes[i].edgeList) {
				nodes[i].edgeList.add(ind);
			}
			
			for(j=0; j<currNumNodes; j++) {
				this.edgeMat[i][j] = eg.edgeMat[i][j];
				this.dist[i][j] = eg.dist[i][j];
			}

			selectedEdgeIndexList.add( new ArrayList<Integer>() );
			selectedEndPointsList.add( new ArrayList<Integer>() );
		}		
		
		for(i=0; i<currNumNodes; i++)
			for(j=i+1; j<currNumNodes; j++)
				if(edgeMat[i][j]) {
					edgeEndPointsList.add(i);
					edgeEndPointsList.add(j);
				}
	}

	private void addNodeToGraph() {
		generateTargetPoints();
		int j = currNumNodes;
		nodes[j] = new Node();
		nodes[j].age = (currNumNodes - initNumNodes)+1;
		for(int i: targetEndPointsList) {
			edgeEndPointsList.add(i);
			edgeEndPointsList.add(j);
			numEdges++;
			edgeMat[i][j] = edgeMat[j][i] = true;
			dist[i][j] = dist[j][i] = 1;
			nodes[i].deg++;
			nodes[j].deg++;
			nodes[i].edgeList.add(j);
			nodes[j].edgeList.add(i);
		}
	}
	
	private void generateTargetPoints() {
		targetEndPointsList = new ArrayList<Integer>();
		
		int i, lenEedgeList;
		int ind, r;
		Random rand = new Random();
		
		lenEedgeList = edgeEndPointsList.size();
		if(lenEedgeList < numEdgeToSelect)
			return;
		
		for(i=0; i<numEdgeToSelect; i++) {
			ind = rand.nextInt(lenEedgeList);
			r = edgeEndPointsList.get(ind);
			if(targetEndPointsList.contains(r)){
				i--;
				continue;
			}
			targetEndPointsList.add(r);
		}
		
		selectedEndPointsList.add( targetEndPointsList );
	}
	
	public void drawGraph(int currWidth, int currHeight, Graphics g) {

		if( drawSimulateGraph ) {
			drawSimulationGraph(currWidth, currHeight, g);
			
			if(currNumNodes == numNodes) {
				contSimulation = false;
				drawSimulateGraph = false;
				currNumNodes = initNumNodes;
			}
			else {
				contSimulation = true;
				drawSimulateGraph = true;
			}
			
			currNumNodes++;
		}
		
		else {
			drawFinalGraph(currWidth, currHeight, g);
			drawSimulateGraph = false;
		}
	}

	public void drawSimulationGraph(int currWidth, int currHeight, Graphics g) {
		int i, j;
		
		for (i=0; i<currNumNodes-1; i++)	{
				MyDrawUtil.drawNode(nodes[i].X, nodes[i].Y, nodeDiam, Color.LIGHT_GRAY, g);
		}
		
		for (i=0; i<currNumNodes-2; i++) {
			for (j=i+1; j<currNumNodes-1; j++) {
				if ( edgeMat[i][j] ) {
					MyDrawUtil.drawEdge(nodes[i], nodes[j], nodeRad, Color.LIGHT_GRAY, g);
				}
			}
		}
		
		delaySomeTime();
		i = currNumNodes-1;
		MyDrawUtil.drawNode(nodes[i].X, nodes[i].Y, nodeDiam+4, Color.RED, g);
		//MyDrawUtil.drawNodeWithBorder(nodes[i].X, nodes[i].Y, nodeDiam+4, Color.RED, g);
			
		delaySomeTime();
		List<Integer> selEndPointsList = selectedEndPointsList.get(currNumNodes-1);
		for(int ind: selEndPointsList) {
			MyDrawUtil.drawNode(nodes[ind].X, nodes[ind].Y, nodeDiam+4, Color.GREEN, g);
			//MyDrawUtil.drawNodeWithBorder(nodes[ind].X, nodes[ind].Y, nodeDiam+4, Color.GREEN, g);
		}

		delaySomeTime();
		for(int ind: selEndPointsList) {
			MyDrawUtil.drawEdge(nodes[i], nodes[ind], nodeRad, Color.BLACK, g);
		}

	}
	
	private void delaySomeTime() {
		try {
			if((currNumNodes - initNumNodes) > numSlowNodes)
				Thread.sleep(QUICK_DELAY);
			else
				Thread.sleep(MAX_DELAY);
		}
		catch (InterruptedException e) {
			System.out.println(e.getStackTrace());
			return;
		}
	}
	
	public void drawFinalGraph(int currWidth, int currHeight, Graphics g) {
		
		if(isHeightWidthChanged(currWidth, currHeight) || !this.isCoordinatesGenerated) {
			MyDrawUtil.generateCoordinatesForNodes(currWidth, currHeight, nodes, numNodes);
			isCoordinatesGenerated = true;
		}
		
		computeDegreeList();
		int maxDegVal = Integer.MAX_VALUE;
		if(this.degreeValueList.size() > 0) 
			maxDegVal = this.degreeValueList.get( this.degreeValueList.size()-1 );
		           
		Color c;
		
		for (int i=0; i<numNodes; i++)	{
			if(nodes[i].deg >= maxDegVal && maxDegVal > 0)
				MyDrawUtil.drawNode(nodes[i].X, nodes[i].Y, nodeDiam+4, Color.GREEN, g);
			else
				MyDrawUtil.drawNode(nodes[i].X, nodes[i].Y, nodeDiam, Color.RED, g);
		}
		
		
		for (int i=0; i<numNodes-1; i++) {
			for (int j=i+1; j<numNodes; j++) {
				if ( edgeMat[i][j] ) {
					if( (nodes[i].deg >= maxDegVal || nodes[j].deg >= maxDegVal) && maxDegVal > 0)
						c = Color.BLACK;
					else 
						c = Color.LIGHT_GRAY;					
					MyDrawUtil.drawEdge(nodes[i], nodes[j], nodeRad, c, g);
				}
			}
		}
	}


	public void writeInfo(LogWriter lw, LogType lt) {
		String dirName, fileName;
		
		dirName = lw.outputDirectory + folderName;
		if( !lw.createDirectory(dirName) )
			return;
		
		fileName = dirName + "/" + lw.getLogTypeFileName(lt);		

		if(lt == LogType.DegreeDistribution || lt == LogType.AgeDegreeRelation) {
				switch(lt) {
					case AgeDegreeRelation:		
						dirName  = dirName + "/" + lw.ageDegreeRelationFolderName;		break;
					case DegreeDistribution:
						dirName  = dirName + "/" + lw.degDistFolderName;		break;
				}
				
				if( !lw.createDirectory(dirName) )
					return;
				int ind = DirUtil.getLastFileNameIndexInDirectory(dirName);
				fileName = dirName + "/" + Integer.toString(ind+1) + ".txt";
		}

		if(!lw.openWriter(fileName))
			return;

		switch(lt) {
			case Summary:				writeSummary(lw);				break;
			case AgeDegreeRelation:		writeAgeDegreeRelation(lw);		break;
			case DegreeDistribution:	writeDegreeDistribution(lw);	break;
		}
		
		lw.closeWriter();
	}

	private void writeSummary(LogWriter lw) {
		DecimalFormat df = new DecimalFormat("#.########");
		
		double avgDeg = 0.0;
		if (numNodes != 0) 
			avgDeg = (double)(numEdges*2)/(double)numNodes;
		
		lw.write( String.format("%10s", numNodes) );
		lw.write( String.format("%10s", initNumNodes) );
		lw.write( String.format("%10s", numEdgeToSelect) );
		lw.write( String.format("%10s", numEdges) );
		lw.write( String.format("%10s", diameter) );
		lw.write( String.format("%10s", numConComp) );
		lw.write( String.format("%10s", maxSizeConComp) );
		lw.write( String.format("%16s", df.format(avgClusCo)) );
		lw.write( String.format("%16s", df.format(avgDeg)) );
		lw.writeLine();
	}

}
